<?php
class DebugHelper
{	
	static public $debugHelperDir;
	
	static public $initialized = false;

	static public $lang = 'zh';
	
	static public function init()
	{		
		$debugHelperDir = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'debug_helper' . DIRECTORY_SEPARATOR;
		include($debugHelperDir . 'DebugFunction.php');
		include($debugHelperDir . 'ErrorMessage.php');
		include($debugHelperDir . 'ErrorMessage' . ucfirst(self::$lang) . '.php');
		self::$initialized = true;
	}

	static public function debug($errorName, $debugData = null)
	{
		if (false == self::$initialized)
		{
			self::init();
		}
		$debugFunctionInstance = new DebugFunction();
		if (method_exists($debugFunctionInstance, $errorName))
		{
			call_user_func(array($debugFunctionInstance, $errorName), $debugData);
		}
		else
		{
			echo '<xmp>';
			print_r($debugData);
			echo '</xmp>';
		}
		trigger_error($errorName, E_USER_ERROR);
	}
}

class KiwiSystemCache
{
	static public $opcodeCacheInstance;

	static public function getAppOptionCacheKey($projectDir, $appName)
	{
		return 'kiwi_app_option_' . crc32($projectDir . $appName);
	}

	static public function getConfigCacheKey($projectDir, $appName = null)
	{
		return 'kiwi_conf_' . crc32($projectDir . $appName);
	}

	static public function getClassFileMappingCacheKey($projectDir, $appName)
	{
		return 'kiwi_class_file_map_' . crc32($projectDir . $appName);
	}

	static private function _getAppOptionPath($key, $userOption, $defaultValue, $onFailed = 'set_null')
	{
		if (!isset($userOption[$key]))
		{
			if (file_exists($defaultValue))
			{
				$appOptionPath = $defaultValue;
			}
			else
			{
				if ('set_null' == $onFailed)
				{
					$appOptionPath = null;
				}
				else
				{
					DebugHelper::debug('APP_OPTION_NOT_SET', array('key' => $key, 'default_value' => $defaultValue));
				}
			}
		}
		else
		{
			if (file_exists($userOption[$key]))
			{
				$appOptionPath = is_dir($userOption[$key]) ? KiwiUtil::getPathWithDirSeparator(realpath($userOption[$key])) : realpath($userOption[$key]);
			}
			else
			{
				DebugHelper::debug('APP_OPTION_INCORRECT', array('key' => $key, 'user_value' => $userOption[$key]));
			}
		}
		return $appOptionPath;
	}

	static public function getAppOption($cacheKey, $userOption, $underMVCMode)
	{
		if ('dev' == Kiwi::$envMode || !self::$opcodeCacheInstance || !($appOptions = self::$opcodeCacheInstance->get($cacheKey)))
		{
			$appOptions = $userOption;
			$appOptions['kiwi_ext_dir'] = self::_getAppOptionPath('kiwi_ext_dir', $userOption, KiwiUtil::getPathWithDirSeparator(dirname(__FILE__)) . 'extension' . DIRECTORY_SEPARATOR, 'debug');
			if ('dev' != Kiwi::$envMode)
			{
				$appOptions['dist_dir'] = self::_getAppOptionPath('dist_dir', $userOption, $appOptions['proj_dir'] . 'dist' . DIRECTORY_SEPARATOR, 'debug');
			}
			if ($underMVCMode)
			{
				$appOptions['app_dir'] = self::_getAppOptionPath('app_dir', $userOption, $appOptions['proj_dir'] . 'app' . DIRECTORY_SEPARATOR . $appOptions['app_name'] . DIRECTORY_SEPARATOR, 'debug');
				$appOptions['app_lib_dir'] = self::_getAppOptionPath('app_lib_dir', $userOption, $appOptions['app_dir'] . 'lib' . DIRECTORY_SEPARATOR);
				$appOptions['app_config_file'] = self::_getAppOptionPath('app_config_file', $userOption, $appOptions['app_dir'] . 'conf' . DIRECTORY_SEPARATOR . 'conf.php');
			}
			$appOptions['config_file'] = self::_getAppOptionPath('config_file', $userOption, $appOptions['proj_dir'] . 'conf' . DIRECTORY_SEPARATOR . 'conf.php');
			$appOptions['proj_lib_dir'] = self::_getAppOptionPath('proj_lib_dir', $userOption, $appOptions['proj_dir'] . 'lib' . DIRECTORY_SEPARATOR);
			if ('dev' != Kiwi::$envMode && self::$opcodeCacheInstance)
			{
				self::$opcodeCacheInstance->add($cacheKey, $appOptions);
			}
		}
		return $appOptions;
	}

	static public function getConfig($cacheKey, $configFile, $defaultConfig = array())
	{
		if ('dev' == Kiwi::$envMode || !self::$opcodeCacheInstance || !($config = self::$opcodeCacheInstance->get($cacheKey)))
		{
			if ($configFile)
			{
				$config = include($configFile);
				if (is_array($config))
				{
					foreach($defaultConfig as $configKey => $configValue)
					{
						if (isset($config[$configKey]))
						{
							$config[$configKey] = array_merge($defaultConfig[$configKey], $config[$configKey]);
						}
						else
						{
							$config[$configKey] = $defaultConfig[$configKey];
						}
					}
					if (isset($config["db_table"]))
					{
						Config::$app = $config;
						Db::setDefaultConfig(array('charset' => Config::$app['misc']['encoding']));
						foreach ($config["db_table"] as $table => $iDontCare)
						{
							$tableInstance = Db::newDbTable($table);
							$config["db_table"][$table]["fields"] = $tableInstance->db->showFields($tableInstance->tableName);
							foreach($config["db_table"][$table]["fields"] as $field)
							{
								if ($field['primary'] == 1)
								{
									$config["db_table"][$table]["primary_key"] = $field['name'];
									break;
								}
							}
						}
					}
				}
				else
				{
					DebugHelper::debug('CONFIG_FILE_DOES_NOT_RETURN_ARRAY', array('config_file' => $configFile));
				}
			}
			else
			{
				$config = $defaultConfig;
			}
			if ('dev' != Kiwi::$envMode && self::$opcodeCacheInstance)
			{
				self::$opcodeCacheInstance->add($cacheKey, $config);
			}
		}
		return $config;
	}

	static public function getClassFileMapping($cacheKey, $directories)
	{
		if ('dev' == Kiwi::$envMode || !self::$opcodeCacheInstance || !($mapping = self::$opcodeCacheInstance->get($cacheKey)))
		{
			while ($directories)
			{
				$currentDir = array_shift($directories);
				if ($dirHandle = opendir($currentDir))
				{
					while (false !== ($file = readdir($dirHandle)))
					{
						if ('.' != $file && '..' != $file && '.svn' != $file)
						{
							$currentFile = $currentDir . $file;
							$extension = pathinfo($file, PATHINFO_EXTENSION);
							if ($extension == 'php' || $extension == 'inc')// we will judge the file extension, we only accept 'php' and 'inc' file.
							{
								$currentFileContent = trim(file_get_contents($currentFile));
								if(preg_match_all('~^\s*(?:abstract\s+|final\s+)?(?:class|interface)\s+(\w+)(\s+(extends|implements)\s+(\w+))?\s*{~mi', $currentFileContent, $classes) > 0) 
								{
									foreach($classes[1] as $key => $class) 
									{
										$mapping[$class] = $currentFile;
									}	
								}
								else
								{
									include($currentFile);
								}
							}
							// if $currentFile is a directory, pass through the next loop.
							else if(is_dir($currentFile))
							{
								$directories[] = $currentFile . DIRECTORY_SEPARATOR;
							}
						}
					}
				}
			}
			if ('dev' != Kiwi::$envMode && self::$opcodeCacheInstance)
			{
				self::$opcodeCacheInstance->add($cacheKey, $mapping);
			}
		}
		return $mapping;
	}
}

class KiwiUtil
{
	/**
	 * Get the dir path with directory separators
	 * @param $path string
	 * @return string
	 */
	static public function getPathWithDirSeparator($path)
	{
		return '\\' == substr($path, -1) || '/' == substr($path, -1) ? $path : $path . DIRECTORY_SEPARATOR;
	}
}

class OpcodeCacheAdapterApc
{
	static public function add($cacheKey, $cacheValue)
	{
		return apc_add($cacheKey, $cacheValue);
	}

	static public function get($cacheKey)
	{
		return apc_fetch($cacheKey);
	}

	static public function delete($cacheKey)
	{
		return apc_delete($cacheKey);
	}
}

class OpcodeCacheAdapterFactory
{
	static public function factory($opcodeCacheExtension = 'apc')
	{
		$opcodeCacheClass = 'OpcodeCacheAdapter' . ucfirst($opcodeCacheExtension);
		return extension_loaded($opcodeCacheExtension) ? new $opcodeCacheClass : null;
	}
}

class OpcodeCacheAdapterXcache
{
	static public function add($cacheKey, $cacheValue)
	{
		return xcache_set($cacheKey, $cacheValue);
	}

	static public function get($cacheKey)
	{
		return xcache_get($cacheKey);
	}

	static public function delete($cacheKey)
	{
		return xcache_unset($cacheKey);
	}
}

class Kiwi
{
	static public $appOptions;
	
	static public $classFileMapping;

	static public $envMode = 'dev';
	
	static public $defaultConfig = array('misc' => array('encoding' => 'UTF-8'),
			'route' => array('rewrite' => 'off',
				'url_pattern' => 'standard'),
			'validator' => array('error_explain' => array('ban' => '%s contain banned words',
					'mask' => '%s does not match the given format',
					'max_length' => '%s is longer than %s',
					'min_length' => '%s is shorter than %s',
					'max_value' => '%s is bigger than %s',
					'min_value' => '%s is smaller than %s',
					'max_selected' => '%s is too much',
					'min_selected' => '%s is too few',
					'required' => '%s is empty',
					'equal_to' => '%s is not equal to %s'
			))
	);	
	
	static public function loadKiwiExt($className)
	{
		if (isset(Kiwi::$classFileMapping[$className]))
		{
			include(Kiwi::$classFileMapping[$className]);
		}
	}
	/**
	 * Set envorinment mode
	 * @param string $envMode
	 */
	static public function setEnvMode($envMode)
	{
		self::$envMode = $envMode;
	}

	/**
	 * Route the user request.
	 */
	static public function route()
	{
		$uri = array();
		if (isset($_SERVER['SERVER_PROTOCOL']))
		{
			if (isset($_REQUEST['module']))
			{
				$uri['module'] = $_REQUEST['module'];
			}
			if (isset($_REQUEST['action']))
			{
				$uri['action'] = $_REQUEST['action'];
			}
		}
		else
		{
			$i = 0;
			while ((!isset($uri['module']) || !isset($uri['action'])) && isset($_SERVER['argv'][$i]))
			{
				if (("-m" == $_SERVER['argv'][$i] || "--module" == $_SERVER['argv'][$i]) && isset($_SERVER['argv'][$i+1]))
				{
					$uri['module'] = $_SERVER['argv'][$i+1];
				}
				else if (("-a" == $_SERVER['argv'][$i] || "--action" == $_SERVER['argv'][$i]) && isset($_SERVER['argv'][$i+1]))
				{
					$uri['action'] = $_SERVER['argv'][$i+1];
				}
				$i ++;
			}
		}
		if (isset($uri['module']) && !preg_match('/^[a-zA-Z0-9\.\-_]+$/', $uri['module']))
		{
			if (function_exists('onModuleNameIllegal'))
			{
				call_user_func('onModuleNameIllegal', $uri['module']);
			}
			else
			{
				throw new Exception("Module name is illegal: {$uri['module']}");
			}
		}
		if (isset($uri['action']) && !preg_match('/^[a-zA-Z0-9\.\-_]+$/', $uri['action']))
		{
			if (function_exists('onaActionNameIllegal'))
			{
				call_user_func('onaActionNameIllegal', $uri['action']);
			}
			else
			{
				throw new Exception("Action name is illegal: {$uri['action']}");
			}
		}
		return $uri;
	}

	static public function init($appOptions, $underMVCMode)
	{
		self::$appOptions = $appOptions;
		KiwiSystemCache::$opcodeCacheInstance = isset(self::$appOptions['opcode_cache_ext']) ? OpcodeCacheAdapterFactory::factory(self::$appOptions['opcode_cache_ext']) : OpcodeCacheAdapterFactory::factory();

		/**
		 * Get cached app option from opcode cache
		 */
		if (!isset(self::$appOptions['proj_dir']) || !isset(self::$appOptions['app_name']))
		{
			$includedFiles = get_included_files();
			$appBootStrap = $includedFiles[0];
		}
		self::$appOptions['proj_dir'] = isset(self::$appOptions['proj_dir']) ? KiwiUtil::getPathWithDirSeparator(self::$appOptions['proj_dir']) : KiwiUtil::getPathWithDirSeparator(dirname(dirname(dirname($appBootStrap))));
		self::$appOptions['app_name'] = isset(self::$appOptions['app_name']) ? self::$appOptions['app_name'] : basename($appBootStrap, '.php');
		$AppOptionCacheKey = KiwiSystemCache::getAppOptionCacheKey(self::$appOptions['proj_dir'], self::$appOptions['app_name']);
		self::$appOptions = KiwiSystemCache::getAppOption($AppOptionCacheKey, self::$appOptions, $underMVCMode);

		/**
		 * Calculate the "class -> file" mapping
		 */
		$classFileMappingCacheKey = KiwiSystemCache::getClassFileMappingCacheKey(self::$appOptions['proj_dir'], self::$appOptions['app_name']);
		$classDirectories = $underMVCMode ? array(self::$appOptions['kiwi_ext_dir'], self::$appOptions['proj_lib_dir'], self::$appOptions['app_lib_dir']) : array(self::$appOptions['kiwi_ext_dir']);
		self::$classFileMapping = KiwiSystemCache::getClassFileMapping($classFileMappingCacheKey, $classDirectories);
		
		/**
		 * Register autoloader
		 */
		spl_autoload_register(array(__CLASS__, "loadKiwiExt"));

		/**
		 * Init project/app configutation
		 */
		
		if ($underMVCMode)
		{
			$projectConfigCacheKey = KiwiSystemCache::getConfigCacheKey(self::$appOptions['proj_dir']);
			$projectConfig = KiwiSystemCache::getConfig($projectConfigCacheKey, self::$appOptions['config_file'], self::$defaultConfig);
			$appConfigCacheKey = KiwiSystemCache::getConfigCacheKey(self::$appOptions['proj_dir'], self::$appOptions['app_name']);
			$appConfig = KiwiSystemCache::getConfig($appConfigCacheKey, self::$appOptions['app_config_file'], $projectConfig);
			Config::$app = $appConfig;
		}
	}

	/**
	 * Run kiwiphp app
	 *
	 * @param array $appOptions
	 * @param string $module
	 * @param string $action
	 */
	static public function run($appOptions = null, $module = null, $action = null)
	{
		/**
		 * Get module and action
		 */
		$uri = self::route();
		if (isset($uri['module']))
		{
			$module = $uri['module'];
		}
		if (isset($uri['action']))
		{
			$action = $uri['action'];
		}
		$underMVCMode = $module && $action ? true : false;
		self::init($appOptions, $underMVCMode);
		/**
		 * Set the internal encoding
		 */
		if(extension_loaded("mbstring"))
		{
			mb_internal_encoding(Config::$app['misc']['encoding']);
		}

		if($underMVCMode)
		{
			Dispatcher::dispatchAction($module, $action);
		}
	}
}
